from collections.abc import Callable


def bisection(function: Callable[[float], float], lower: float, upper: float, epsilon: float = 10 ** -7) -> float:
    """
    使用二分法寻找函数在指定区间内的零点。

    参数:
    function: Callable[[float], float] - 目标函数，接受一个浮点数并返回一个浮点数。
    lower: float - 区间下界。
    upper: float - 区间上界。
    epsilon: float - 精度误差，默认为10的-7次方。

    返回:
    float - 函数在指定区间内的零点，结果精确到epsilon。

    Raises:
    ValueError: 如果函数在区间两端的值符号相同，则抛出ValueError。
    """
    # 检查区间两端的函数值是否符号相反
    if function(lower) * function(upper) >= 0:
        raise ValueError("The function must have opposite signs at the endpoints.")

    # 检查区间两端是否已经为零点
    if function(lower) == 0:
        return lower
    if function(upper) == 0:
        return upper

    # 通过二分法寻找零点
    while abs(upper - lower) > epsilon:
        mid = (lower + upper) / 2
        # 检查中点是否为零点
        if function(mid) == 0:
            return mid
        # 根据中点函数值的符号，缩小搜索区间
        elif function(mid) * function(lower) < 0:
            upper = mid
        else:
            lower = mid

    # 返回最终找到的零点
    return lower


def f(x: float) -> float:
    """
    目标函数，用于测试二分法。

    参数:
    x: float - 输入值。

    返回:
    float - 函数值。
    """
    return x ** 3 - 2 * x - 5


if __name__ == "__main__":
    print(bisection(f, 1, 10))
